/**
 * 
 */
package com.sogeti.mentormatch.dao.hibernate;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import com.sogeti.mentormatch.aop.QueryFinderExecutor;
import com.sogeti.mentormatch.aop.QueryFinderNamingStrategy;
import com.sogeti.mentormatch.aop.spring.SimpleQueryFinderNamingStrategy;

import org.hibernate.Query;
import org.hibernate.type.Type;

/**
 * @author Matthew L. Maurer <maurer.it@gmail.com
 */
public class QueryFindingGenericSpringHibernateDAO<DOBJECT, PKEY extends Serializable>
        extends GenericSpringHibernateDAO<DOBJECT, PKEY> implements
    QueryFinderExecutor<DOBJECT>
{
    // Can override with spring application context
    private QueryFinderNamingStrategy namingStrategy = new SimpleQueryFinderNamingStrategy();

    /**
     * @param clazz
     */
    public QueryFindingGenericSpringHibernateDAO ( Class<DOBJECT> clazz ) {
        super(clazz);
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.sogeti.mentormatch.aop.QueryFinderExecutor#executeQueryFinder(java.lang.reflect.Method,
     *      java.lang.Object[])
     */
    @SuppressWarnings ( "unchecked" )
    public List<DOBJECT> executeQueryFinder ( Method method, Object[ ] queryArgs )
    {
        final Query namedQuery = prepareQuery(method, queryArgs);
        return ( List<DOBJECT> ) namedQuery.list();
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.sogeti.mentormatch.aop.QueryFinderExecutor#iterateQueryFinder(java.lang.reflect.Method,
     *      java.lang.Object[])
     */
    @SuppressWarnings ( "unchecked" )
    public Iterator<DOBJECT> iterateQueryFinder ( Method method,
            Object[ ] queryArgs )
    {
        final Query namedQuery = prepareQuery(method, queryArgs);
        return ( Iterator<DOBJECT> ) namedQuery.iterate();
    }

    @SuppressWarnings ( { "unused", "unchecked" } )
    private Query prepareQuery ( Method method, Object[ ] queryArgs ) {
        final String queryName = getNamingStrategy()
                .getQueryNameFromMethodCall(clazz, method);
        final Query namedQuery = getSession().getNamedQuery(queryName);
        String[ ] namedParameters = namedQuery.getNamedParameters();
        if ( namedParameters.length == 0 ) {
            setPositionalParams(queryArgs, namedQuery);
        }
        else {
         // we should be handling a Map for named parameters, validate that.
            assert queryArgs.length == 1;
            assert queryArgs[ 0 ] instanceof Map;

            // we should be in the clear to make the substitution.
            setNamedParams(namedParameters, ( Map ) queryArgs[ 0 ], namedQuery);
        }
        return namedQuery;
    }

    private void setPositionalParams ( Object[ ] queryArgs, Query namedQuery ) {
        // Set parameter. Use custom Hibernate Type if necessary
        if ( queryArgs != null ) {
            for ( int i = 0; i < queryArgs.length; i++ ) {
                Object arg = queryArgs[ i ];

                // TODO Determine if ArgumentTypeFactory is really needed
                Type argType = null; // getArgumentTypeFactory().getArgumentType(arg);

                // never used
                if ( argType != null ) {
                    namedQuery.setParameter(i, arg, argType);
                }
                else {
                    namedQuery.setParameter(i, arg);
                }
            }
        }
    }

    /**
     * @param namedParameters
     * @param queryArgs
     * @param namedQuery
     * @see https://secure.maurer-it.net/projects/maurer-it.net/browser/jbudgetmanager/trunk/jbudgetmanager/jbm-persistence/src/main/java/net/maurerit/jbudgetmanager/dao/hibernate/QueryFindingGenericSpringHibernateDAO.java?rev=45#L98
     */
    @SuppressWarnings ( "unchecked" )
    private void setNamedParams ( String[ ] namedParameters, Map queryArgs,
            Query namedQuery )
    {
        Object namedArgSubstitution = null;

        if ( queryArgs != null ) {
            for ( int i = 0; i < namedParameters.length; i++ ) {
                namedArgSubstitution = queryArgs.get(namedParameters[ i ]);

                if ( namedArgSubstitution instanceof Collection ) {
                    namedQuery.setParameterList(namedParameters[ i ],
                            ( Collection ) namedArgSubstitution);
                }
                else {
                    namedQuery.setParameter(namedParameters[ i ],
                            namedArgSubstitution);
                }
            }
        }
    }

    public QueryFinderNamingStrategy getNamingStrategy ( ) {
        return namingStrategy;
    }

    public void setNamingStrategy ( QueryFinderNamingStrategy namingStrategy ) {
        this.namingStrategy = namingStrategy;
    }

}
